﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using Effects.Wave;
using Microsoft.DirectX.DirectSound;

namespace Effects
{
    // Interface used to play wave files. Different technologies can be used via implementing this interface
    public interface AudioPlayer
    {
        int GetPlayPosition();
        
        void SetPlayPosition(int position);

        int GetWaveLength();

        void Play();

        void Pause();

        void Stop();
    }
    
    // Class implementing AudioPlayer interface using Managed DirectSound library.
    // Main idea is to use small circular buffer as source for DirectSound.SecondaryBuffer. 
    class EffectStreamPlayer : AudioPlayer
    {
        // Wave instace to be played
        private Wave.Wave wav;
        
        // Effect Stream is used as source
        private EffectStream input;

        // timer that ensures that buffer is filled regularly
        private System.Timers.Timer timer;

        // SecondaryBuffer provides playing the buffer
        private SecondaryBuffer sBuffer;

        // size of the buffer
        private int bufferSize;
        
        // position to write to the circular buffer for the next time
        private int nextWrite;
        
        // play position, int the wav
        private int playPosition;

        // maximum latency that can happen when playing
        // property doesn't store latency length itself (in miliseconds) but the corresponging amount of bytes according to Wave format
        private int maxLatency;

        // constructor
        public EffectStreamPlayer(Control owner,Wave.Wave wav,global::Effects.GUI.EffectBoard effects)
        {
            this.wav = wav;
            maxLatency = MSToBytes(30);

            // creat new sound device
            Device device = new Device();
            device.SetCooperativeLevel(owner, CooperativeLevel.Normal);

            // create format description
            BufferDescription descr = new BufferDescription(WaveFormatFromFmtChunk(wav.FmtChunk));
            descr.BufferBytes = Convert.ToInt32(wav.FmtChunk.AvgBytesPerSec/4);
            descr.ControlVolume = true;
            descr.GlobalFocus = true;

            // create new SecondaryBuffer
            sBuffer = new SecondaryBuffer(descr, device);
            //sBuffer = new SecondaryBuffer("pok3.wav", device);
            bufferSize = sBuffer.Caps.BufferBytes;

            // create new timer
            //timer = new System.Timers.Timer(BytesToMS(bufferSize)/20);
            timer = new System.Timers.Timer(15);
            timer.Enabled = false;
            timer.Elapsed += new System.Timers.ElapsedEventHandler(this.TimerCall);

            // create EffectStream to get data from somewhere
            input = new EffectStream(wav, effects, 5, bufferSize);
            input.Start();

            Stop();
        }

        // method converts Effects.Wave.FmtChunk to DirectSound.WaveFormat
        private WaveFormat WaveFormatFromFmtChunk(FmtChunk fmt)
        {
            WaveFormat format = new WaveFormat();
            
            format.Channels = Convert.ToInt16(fmt.Channels);
            format.SamplesPerSecond = Convert.ToInt32(fmt.SampleRate);
            format.BitsPerSample = Convert.ToInt16(fmt.BitsPerSample);
            format.BlockAlign = Convert.ToInt16(fmt.FrameSize);
            format.AverageBytesPerSecond = Convert.ToInt32(fmt.AvgBytesPerSec);
            format.FormatTag = WaveFormatTag.Pcm; // fmt.Format;

            return format;
        }

        bool stopped;

        // method starts playback
        // It puts sumething to the buffer and runs the timer
        public void Play()
        {
            if (stopped)
            {
                stopped = false;
                input.Start();
                //System.Threading.Thread.Sleep(100);
                FeedFromPlayPosition(bufferSize / 10);
            }

            timer.Enabled = true;
            sBuffer.Play(0, BufferPlayFlags.Looping);
        }

        // method stops playback
        public void Stop()
        {
            stopped = true;
            // stop Secondary Buffer            
            if (sBuffer != null)
            {
                sBuffer.Stop();
                //sBuffer.SetCurrentPosition(0);
            }
            
            // stop timer from ticking
            if (timer != null)
                timer.Enabled = false;
            
            
            // reset position in udelying EffectStream
            input.Stop();
            //ClearBuffer();

            // reset position
            playPosition = 0;
            //nextWrite = 0;
        }

        // method pauses the playback
        public void Pause()
        {
            if (timer != null)
                timer.Enabled = false;
            if (sBuffer != null)
                sBuffer.Stop();
        }

        public void SetPlayPosition(int position)
        {
            try
            {
                input.SetPosition(position);
            }
            catch (ArgumentException)
            { }
        }

        public int GetPlayPosition()
        {
            return input.Position;
        }

        public int GetWaveLength()
        {
            return input.Length;
        }

        // returns amount that have been played since last feed
        private int GetPlayedSize()
        {
            int pos = sBuffer.PlayPosition;
            return
               pos < nextWrite ?
               pos + bufferSize - nextWrite
               : pos - nextWrite;
        }

        // Method fills the buffer. It gets data from input EffectStream and copies it to the circular buffer
        private void Feed(int bytes)
        {
            // limit latency
            int copyCount = Math.Min(bytes, maxLatency);
            //int copyCount = 100;
            //int copyCount = bytes;

            if (copyCount > 0)
            {
                // restore buffer if needed
                if (sBuffer.Status.BufferLost)
                    sBuffer.Restore();

                // get data from stream
                byte[] getArray = input.Get(copyCount);

                try
                {
                    sBuffer.Write(nextWrite, getArray, LockFlag.None);

                    /*int freeBufferBytes = bufferSize - nextWrite;
                    if (freeBufferBytes > copyCount)
                    {
                        sBuffer.Write(nextWrite, input.Get(copyCount), LockFlag.None);
                    }
                    else
                    {
                        byte[] getArray=input.Get(copyCount);

                        byte[] firstPart = new byte[freeBufferBytes];
                        byte[] secondPart = new byte[copyCount-freeBufferBytes];

                        Array.Copy(getArray, 0, firstPart, 0, freeBufferBytes);
                        Array.Copy(getArray, freeBufferBytes, secondPart, 0, copyCount - freeBufferBytes);

                        sBuffer.Write(nextWrite, firstPart, LockFlag.None);
                        sBuffer.Write(0, secondPart, LockFlag.None);
                    }*/
                }
                catch (ArgumentException)
                {
                    Console.WriteLine("Error");
                }

                // count write position for next feed
                nextWrite += copyCount;
                if (nextWrite > bufferSize)
                    nextWrite -= bufferSize;
            }
        }

        private void FeedFromPlayPosition(int bytes)
        {
            // limit latency
            int copyCount = Math.Min(bytes, maxLatency);

            if (copyCount > 0)
            {
                // restore buffer if needed
                if (sBuffer.Status.BufferLost)
                    sBuffer.Restore();

                // get data from stream
                byte[] getArray = input.Get(copyCount);
                int position = sBuffer.PlayPosition;

                try
                {

                    sBuffer.Write(position, getArray, LockFlag.None);

                }
                catch (ArgumentException)
                {
                    Console.WriteLine("Error");
                }

                // count write position for next feed
                nextWrite = position + copyCount;
                if (nextWrite > bufferSize)
                    nextWrite -= bufferSize;
            }
        }

        private void ClearBuffer()
        {
            // it fills circular buffer with zeroes
            if (sBuffer != null)
            {
                byte[] clear = new byte[sBuffer.Caps.BufferBytes];
                sBuffer.Write(0, clear, LockFlag.None);
            }

        }

        // timer call, time to feed again and prepare another data in EffectStream
        private void TimerCall(object sender, System.Timers.ElapsedEventArgs e)
        {
            // check the end
            if (input.Position == input.Length)
            {
                Stop();
            }

            int size = GetPlayedSize();
            Feed(size);
            //input.Buffer(size);
        }


        // mothods converting bytes count to miliseconds (and back) according to wave format
        private int BytesToMS(int bytes)
        {
            int oneKMilis= Convert.ToInt32(wav.FmtChunk.SampleRate * wav.FmtChunk.FrameSize);
            return (1000 * bytes) / oneKMilis;
        }

        private int MSToBytes(int ms)
        {
            int oneKMilis = Convert.ToInt32(wav.FmtChunk.SampleRate * wav.FmtChunk.FrameSize);
            return (ms * oneKMilis) / 1000;
        }
    }
}
